using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Media;
using Pong.Classes;

namespace Pong
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    /// 

    public class Pong : Microsoft.Xna.Framework.Game
    {
        private const float SCALE_DIVISOR = 1920f;
        private const int PLAYER1 = 1;
        private const int PLAYER2 = 2;
        private const int IA = 3;
        private const int INVICIBLEIA = 4;

        private string _nameP1;
        private string _nameP2;
        private PowerClass _classP1;
        private PowerClass _classP2;
        private Vector2 _nameP1Position;
        private Vector2 _nameP2Position;
        private int _nbPtsForVictoryP1;
        private int _nbPtsForVictoryP2;
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private KeyboardState _keyboardState;
        private MouseState _mouseState;

        private Bat _leftBat;
        private Bat _rightBat;
        private Ball _ball;
        private Texture2D _background;
        private string _mode;
        private bool _survival = false;
        private bool _survivalEnded = false;

        // Create a Rectangle that will define the limits for the main game screen.
        private Rectangle _mainFrame;
        private float _scale = 1f;
        private SpriteFont _score;
        private String _tutorial = "Press space bar to start and Esc to exit";
        private Vector2 _tutorialPosition;
        private Song _song;

        SoundEffect _ballWall;


        private bool _gameIsover;
        private bool _player1Won;
        private bool _player2Won;
        private bool _pause;

        // Effect related members
        private Effect lightingEffect;
        private Texture2D lightMask;
        private Texture2D batLight;
        private RenderTarget2D lightsTarget;
        private RenderTarget2D mainTarget;

        public Pong()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = false;
            graphics.PreferredBackBufferWidth = MyConstants.WIDTH_MEDIUM_GAME;
            graphics.PreferredBackBufferHeight = MyConstants.HEIGHT_MEDIUM_GAME;
        }


        public Pong(string namep1, string namep2, int size, Boolean isFullScreen, string mode, PowerClass classP1, PowerClass classP2)
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            _gameIsover = false;
            _player1Won = false;
            _player2Won = false;
            graphics.IsFullScreen = isFullScreen;

            if (classP1 is Shinobi)
            {
                _nbPtsForVictoryP2 = MyConstants.POINTS_TO_VICTORY_SHINOBI;
            }
            else
            {
                _nbPtsForVictoryP2 = MyConstants.POINTS_TO_VICTORY_CLASSICAL;
            }

            if (classP2 is Shinobi)
            {
                _nbPtsForVictoryP1 = MyConstants.POINTS_TO_VICTORY_SHINOBI;
            }
            else
            {
                _nbPtsForVictoryP1 = MyConstants.POINTS_TO_VICTORY_CLASSICAL;
            }


            this._mode = mode;

            if (size == (int)format.small)
            {

                graphics.PreferredBackBufferWidth = MyConstants.WIDTH_SMALL_GAME;
                graphics.PreferredBackBufferHeight = MyConstants.HEIGHT_SMALL_GAME;

            }
            else if (size == (int)format.medium)
            {

                graphics.PreferredBackBufferWidth = MyConstants.WIDTH_MEDIUM_GAME;
                graphics.PreferredBackBufferHeight = MyConstants.HEIGHT_MEDIUM_GAME;
            }
            else if (size == (int)format.large)
            {

                graphics.PreferredBackBufferWidth = MyConstants.WIDTH_LARGE_GAME;
                graphics.PreferredBackBufferHeight = MyConstants.HEIGHT_LARGE_GAME;

            }

            _nameP1 = namep1;
            _nameP2 = namep2;
            _classP1 = classP1;
            _classP2 = classP2;
        }

        

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            MediaPlayer.IsMuted = false;
            MediaPlayer.IsRepeating = true;
            MediaPlayer.Volume = MyConstants.VOLUME_MUSIC;

            Boolean isP1Manipulator = false; 
            Boolean isP2Manipulator = false;

            Shinigami mp1 = _classP1 as Shinigami;
            Shinigami mp2 = _classP2 as Shinigami;
            if (mp1 != null)
            {
                isP1Manipulator = true;
            }

            if (mp2 != null)
            {
                isP2Manipulator = true;
            }

            _scale = Window.ClientBounds.Width / SCALE_DIVISOR;

            _leftBat = new Bat(Window.ClientBounds.Width, Window.ClientBounds.Height, _scale, 1, _classP1, isP2Manipulator);
          
            //if 1 player selected, the right bat will be control by CPU
            if (_mode == MyConstants.MODE_SOLO)
            {
                _rightBat = new Bat(Window.ClientBounds.Width, Window.ClientBounds.Height, _scale, IA, _classP2, isP1Manipulator);
            }
            //if 2 players selected, the right bat will be control by keyboard.
            else if (_mode == MyConstants.MODE_MULTI)
            {
                _rightBat = new Bat(Window.ClientBounds.Width, Window.ClientBounds.Height, _scale, PLAYER2, _classP2, isP1Manipulator);
            }
            //if Survival mode selected, the right bat will be control by CPU, but it will become invincible, the goal is to hold the most hit possible.
            else if (_mode == MyConstants.MODE_SURVIVAL)
            {
                _rightBat = new Bat(Window.ClientBounds.Width, Window.ClientBounds.Height, _scale, 4, _classP2, isP1Manipulator);
                _survival = true;
            }

            _ball = new Ball(Window.ClientBounds.Width, Window.ClientBounds.Height, _scale, _survival);

            _pause = true;
            _ball.Initialize();
            _leftBat.Initialize();
            _rightBat.Initialize();
            
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            
            _ball.LoadContent(Content, MyConstants.NAME_BAT_FILE);
            _leftBat.LoadContent(Content, _leftBat.MyClass.ToString());
            _rightBat.LoadContent(Content, _rightBat.MyClass.ToString());
            _background = Content.Load<Texture2D>(MyConstants.NAME_BACKGROUND_FILE);

            // Set the rectangle parameters for main frame
            _mainFrame = new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
            _song = Content.Load<Song>(MyConstants.NAME_SONG_FILE);
            _ballWall = Content.Load<SoundEffect>(MyConstants.EFFECT_SOUND_NAME);
            _score = Content.Load<SpriteFont>(MyConstants.NAME_SCOREFONT_FILE);

            Vector2 tutorialSize = _score.MeasureString(_tutorial) * _scale;
            _tutorialPosition = new Vector2((Window.ClientBounds.Width - tutorialSize.X) / 2, (0.4f * Window.ClientBounds.Height - tutorialSize.Y) / 2);
            Vector2 nameP1Size = _score.MeasureString(_nameP1) * _scale;

            if (_survival)
            {
                _nameP1Position = new Vector2(0.5f * Window.ClientBounds.Width - nameP1Size.X / 2, 0.01f * Window.ClientBounds.Height);
            }
            else
            {
            _nameP1Position = new Vector2(0.25f * Window.ClientBounds.Width - nameP1Size.X / 2, 0.01f * Window.ClientBounds.Height);
            }
            
            Vector2 nameP2Size = _score.MeasureString(_nameP2) * _scale;
            _nameP2Position = new Vector2(0.75f * Window.ClientBounds.Width - nameP2Size.X / 2, 0.01f * Window.ClientBounds.Height);

            MediaPlayer.Play(_song);

            //Effect related content
            lightMask = Content.Load<Texture2D>(MyConstants.EFFECT_LIGHT_FILE_NAME);
            batLight = Content.Load<Texture2D>(MyConstants.EFFECT_BAT_FILE_NAME);
            lightingEffect = Content.Load<Effect>(MyConstants.EFFECT_LIGHTING_FILE_NAME);

            var pp = GraphicsDevice.PresentationParameters;

            lightsTarget = new RenderTarget2D(
                GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight);

            mainTarget = new RenderTarget2D(
                GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            _keyboardState = Keyboard.GetState();
            _mouseState = Mouse.GetState();

            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            // Pause checking
            if (!_pause)
            {
                _leftBat.HandleInput(_keyboardState, _mouseState, _ball);
                _rightBat.HandleInput(_keyboardState, _mouseState, _ball);

                _leftBat.Update(gameTime);
                _rightBat.Update(gameTime);

                _ball.Update(gameTime, _leftBat.CollisionBox, _rightBat.CollisionBox, _ballWall);

                // Goal checking
                CheckIfBallOut();
            }
            else
            {
                // If the game is paused we check the space bar for unpausing
                if (_keyboardState.IsKeyDown(Keys.Space))
                {
                    if (_gameIsover)
                    {
                        _player1Won = false;
                        _player2Won = false;
                        _gameIsover = false;
                        _leftBat.Score = 0;
                        _rightBat.Score = 0;
                    _pause = false;
                }
                    if (_survivalEnded)
                    {
                        _survivalEnded = false;
                        _ball.Initialize();
                        _pause = false;

            }
                    else
                    {
                        _pause = false;
                    }

                }
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// Checks if the ball is out
        /// In this case the score is incremented
        /// Finally a new match is set up
        /// </summary>
        private void CheckIfBallOut()
        {
            if (_ball.Position.X <= 0)
            {
                if (_survival)
                {
                    _survivalEnded = true;
                    _ball.Position = new Vector2(Window.ClientBounds.Width / 2 - _ball.Texture.Width * _scale / 2,
                                                 Window.ClientBounds.Height / 2 - _ball.Texture.Height * _scale / 2);
                }
                else
                {
                _rightBat.Score++;
                _ball.Initialize();
                _ball.Position = new Vector2(Window.ClientBounds.Width / 2 - _ball.Texture.Width * _scale / 2,
                                                 Window.ClientBounds.Height / 2 - _ball.Texture.Height * _scale / 2);
                }
                _pause = true;
            }
            else if (_ball.Position.X >= Window.ClientBounds.Width - _ball.Texture.Width * _scale)
            {
                _leftBat.Score++;
                _ball.Initialize();
                _ball.Position = new Vector2(Window.ClientBounds.Width / 2 - _ball.Texture.Width * _scale / 2,
                                             Window.ClientBounds.Height / 2 - _ball.Texture.Height * _scale / 2);
                _pause = true;
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            // Create a Light Mask to pass to the pixel shader 
            GraphicsDevice.SetRenderTarget(lightsTarget);
            GraphicsDevice.Clear(new Color(MyConstants.SHADER_COLOR_R, MyConstants.SHADER_COLOR_G, MyConstants.SHADER_COLOR_B, MyConstants.SHADER_COLOR_A));

            if (_leftBat.Score == _nbPtsForVictoryP1)
            {
                _gameIsover = true;
                _player1Won = true;
            }

            if (_rightBat.Score == _nbPtsForVictoryP2)
            {
                _gameIsover = true;
                _player2Won = true;
            }


            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);

            spriteBatch.Draw(lightMask,
                new Vector2(_ball.Position.X - (lightMask.Width * _scale / 2) + (_ball.Texture.Width * _scale / 2), _ball.Position.Y - (lightMask.Height * _scale / 2) + (_ball.Texture.Height * _scale / 2)), 
                null,
                Color.White,
                0f,
                Vector2.Zero,
                _scale,
                SpriteEffects.None,
                0f);
            spriteBatch.Draw(batLight,
                new Vector2(_leftBat.Position.X - (batLight.Width * _scale / 2) + (_leftBat.Texture.Width * _scale / 2), _leftBat.Position.Y - (batLight.Height * _scale / 2) + (_leftBat.Texture.Height * _scale / 2)), 
                null,
                Color.White,
                0f,
                Vector2.Zero,
                _scale,
                SpriteEffects.None,
                0f);
            spriteBatch.Draw(batLight,
                new Vector2(_rightBat.Position.X - (batLight.Width * _scale / 2) + (_rightBat.Texture.Width * _scale / 2), _rightBat.Position.Y - (batLight.Height * _scale / 2) + (_rightBat.Texture.Height * _scale / 2)), 
                null,
                Color.White,
                0f,
                Vector2.Zero,
                _scale,
                SpriteEffects.None,
                0f);
            spriteBatch.End();

            // Draw the main scene to the Render Target  
            GraphicsDevice.SetRenderTarget(mainTarget);
            GraphicsDevice.Clear(Color.White);
            spriteBatch.Begin();
            spriteBatch.Draw(_background, _mainFrame, Color.White);
            spriteBatch.End();

            // Draw the main scene with a pixel  
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.White);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

            lightingEffect.Parameters["lightMask"].SetValue(lightsTarget);
            lightingEffect.CurrentTechnique.Passes[0].Apply();
            spriteBatch.Draw(mainTarget, Vector2.Zero, Color.White);

            spriteBatch.End();

            // Score display
            if (_survival)
            {
                spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

                spriteBatch.DrawString(_score, _nameP1, _nameP1Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
                Vector2 survivalScoreSize = _score.MeasureString(_ball.SurvivalScore.ToString()) * _scale;
                Vector2 survivalScorePosition = new Vector2(Window.ClientBounds.Width / 2 - survivalScoreSize.X / 2, survivalScoreSize.Y);
                spriteBatch.DrawString(_score, _ball.SurvivalScore.ToString(), survivalScorePosition, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);

                spriteBatch.End();
            }

            else
            {
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

            spriteBatch.DrawString(_score, _nameP1, _nameP1Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
            spriteBatch.DrawString(_score, _nameP2, _nameP2Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);

                Vector2 scoreSize = _score.MeasureString(_leftBat.Score.ToString()) * _scale;
            Vector2 scoreP1Position = new Vector2(Window.ClientBounds.Width / 4 - scoreSize.X / 2,
                                                  scoreSize.Y);
                spriteBatch.DrawString(_score, _leftBat.Score.ToString(), scoreP1Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);

                scoreSize = _score.MeasureString(_leftBat.Score.ToString()) * _scale;
            Vector2 scoreP2Position = new Vector2(3 * Window.ClientBounds.Width / 4 - scoreSize.X / 2,
                                                  scoreSize.Y);
                spriteBatch.DrawString(_score, _rightBat.Score.ToString(), scoreP2Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
            spriteBatch.End();
            }


            // Tutorial display
            if (_pause) 
            {
                if (_survivalEnded)
                {
                    _tutorial = "Your score is : " + _ball.SurvivalScore + "    Press Esc or Space to start over";
                    Vector2 tutorialSize = _score.MeasureString(_tutorial) * _scale;
                    _tutorialPosition = new Vector2((Window.ClientBounds.Width - tutorialSize.X) / 2, (0.4f * Window.ClientBounds.Height - tutorialSize.Y) / 2);
                }
                    
                if (_gameIsover)
                {
                    if (_player1Won)
                    {
                        string victory = _nameP1 + " Wins !! ";
                        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

                        spriteBatch.DrawString(_score, _nameP1, _nameP1Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
                        Vector2 WinScoreSize = _score.MeasureString(victory) * _scale;
                        Vector2 WinScorePosition = new Vector2(Window.ClientBounds.Width / 2 - WinScoreSize.X / 2, Window.ClientBounds.Height / 2 - WinScoreSize.Y / 2);
                        spriteBatch.DrawString(_score, victory, WinScorePosition, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
                        spriteBatch.End();



                    }
                    else if (_player2Won)
                    {
                        string victory = _nameP2 + " Wins !! ";
                        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
                        spriteBatch.DrawString(_score, _nameP2, _nameP2Position, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
                        Vector2 WinScoreSize = _score.MeasureString(victory) * _scale;
                        Vector2 WinScorePosition = new Vector2(Window.ClientBounds.Width / 2 - WinScoreSize.X / 2, Window.ClientBounds.Height / 2 - WinScoreSize.Y / 2);
                        spriteBatch.DrawString(_score, victory, WinScorePosition, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
                        spriteBatch.End();


                    }

                }

                spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
                spriteBatch.DrawString(_score, _tutorial, _tutorialPosition, Color.White, 0f, Vector2.Zero, _scale, SpriteEffects.None, 0f);
                spriteBatch.End();
            }

            // Sprite display
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

            _ball.Draw(spriteBatch, gameTime, _scale);
            _leftBat.Draw(spriteBatch, gameTime, _scale);
            _rightBat.Draw(spriteBatch, gameTime, _scale);

            spriteBatch.End();
            
            base.Draw(gameTime);
        }
    }
}